<script setup lang="ts">
import { ref, onMounted, onUnmounted, getCurrentInstance } from 'vue'
import { JSMpeg } from './jsmpeg.gai.js'
import {
  FullScreen,
  Menu,
  CaretLeft,
  CaretTop,
  CaretRight,
  CaretBottom,
  HomeFilled,
  VideoPlay,
  VideoPause
} from '@element-plus/icons-vue'
import { ElDatePicker, ElMessage } from 'element-plus'

interface Params {
  // 预览和控制
  webCam: {
    ip: string
    username: string
    password: string
    channel?: number
    subtype?: number
  }
  // 回放
  nvr: {
    ip: string
    username: string
    password: string
    channel?: number
    subtype?: number
  }
}
const props = withDefaults(
  defineProps<{
    wsUrl: string
    params: Params
  }>(),
  {
    wsUrl: 'ws://localhost:9999/'
  }
)

const container = ref()
const canvas = ref()
const poster = ref()
const player = ref()
const controls = ref()
const ptz = ref()
const onLine = ref(false)
let ws = null
let loading = ref(false)
const fetchSnapshotSecond = 10 // 获取快照和检测设备在线的间隔秒数

const switchControls = (className) => {
  controls.value.classList.toggle(className)
}
// pointerdown事件中全屏会出问题，用click或pointerup
const fullScreen = (event) => {
  event.preventDefault()
  event.stopPropagation()

  if (container.value) {
    let isFullscreen = document.fullscreenElement === container.value
    if (isFullscreen) document.exitFullscreen()
    else container.value.requestFullscreen()
  }
}
// onvif
const initOnvif = () => {
  ws.send(
    JSON.stringify({
      method: 'initOnvif',
      params: props.params.webCam
    })
  )
}
// ptz
let lerpVectors = (v1, v2, alpha) => {
  let x = v1.x + (v2.x - v1.x) * alpha
  let y = v1.y + (v2.y - v1.y) * alpha

  return { x, y }
}

/**
 *        ..........
 * point1            point2
 *
 *
 *   point3       point4
 *
 *
 *         center
 */
const computePath = (type: string) => {
  let radius = 100
  let pi_2 = Math.PI / 2
  let pi_4 = Math.PI / 4
  let startAngle = 0
  let endAngle = 0
  let alpha = 0.52

  let center = { x: radius, y: radius }
  let point1 = { x: 0, y: 0 }
  let point2 = { x: 0, y: 0 }

  if (type == 'left') startAngle = Math.PI + pi_4
  if (type == 'top') startAngle = Math.PI * 2 - pi_4
  if (type == 'right') startAngle = pi_4
  if (type == 'bottom') startAngle = Math.PI - pi_4

  endAngle = startAngle + pi_2 - 0.01
  point1.x = radius + Math.sin(startAngle) * radius
  point1.y = radius - Math.cos(startAngle) * radius
  point2.x = radius + Math.sin(endAngle) * radius
  point2.y = radius - Math.cos(endAngle) * radius

  let point3 = lerpVectors(center, point1, alpha)
  let point4 = lerpVectors(center, point2, alpha)

  return {
    'clip-path':
      `path("M ${point3.x} ${point3.y}` +
      `A ${radius * alpha} ${radius * alpha} 0 0 1 ${point4.x} ${point4.y}` +
      `L ${point2.x} ${point2.y}` +
      `A ${radius} ${radius} 0 0 0 ${point1.x} ${point1.y} z")`
  }
}

let ptzMoving = ref(false)

const ptzHome = () => {
  if (ptzMoving.value == true) return
  ptzMoving.value = true

  let ptzParams = {
    timeout: 30
  }
  ws.send(
    JSON.stringify({
      method: 'ptzHome',
      params: ptzParams
    })
  )
}

const ptzMove = (type) => {
  if (ptzMoving.value == true) return
  ptzMoving.value = true

  let ptzParams = {
    speed: { x: 0, y: 0, z: 0 },
    timeout: 30
  }
  let speed = 1

  if (type == 'left') ptzParams.speed.x = 0 - speed
  if (type == 'top') ptzParams.speed.y = speed
  if (type == 'right') ptzParams.speed.x = speed
  if (type == 'bottom') ptzParams.speed.y = 0 - speed
  if (type == 'in') ptzParams.speed.z = 1
  if (type == 'out') ptzParams.speed.z = -1

  ws.send(
    JSON.stringify({
      method: 'ptzMove',
      params: ptzParams
    })
  )
}

const ptzStop = () => {
  ws.send(
    JSON.stringify({
      method: 'ptzStop'
    })
  )

  ptzMoving.value = false
}
// 清空画面
const clearCanvas = () => {
  const canvas1 = canvas.value
  const gl = canvas1.getContext('webgl')
  gl.clearColor(0.0, 0.0, 0.0, 1.0)
  gl.clear(gl.COLOR_BUFFER_BIT)
  gl.clearDepth(1.0)
  gl.enable(gl.DEPTH_TEST)
  gl.clear(gl.DEPTH_BUFFER_BIT)
}
// 开始视频流
const startStream = () => {
  clearCanvas()

  loading.value = true

  ws.send(
    JSON.stringify({
      method: 'startStream',
      params: props.params.webCam
    })
  )

  ptz.value.classList.remove('hidden')
}
// 回放
let startTime = ref('')

const replaceTime = (time) => {
  const timeStr = Intl.DateTimeFormat('zh', {
    year: 'numeric',
    month: '2-digit',
    day: '2-digit',
    hour: '2-digit',
    minute: '2-digit',
    second: '2-digit',
    hour12: false
  }).format(time)

  return (
    timeStr
      .toLocaleString()
      .replace(/[\/\:]/g, '')
      .replace(' ', 't') + 'z'
  )
}

const playback = () => {
  if (!startTime.value) return

  clearCanvas()

  loading.value = true

  let params = {
    startTime: replaceTime(startTime.value),
    endTime: replaceTime(new Date()),
    ...props.params.nvr
  }

  ws.send(
    JSON.stringify({
      method: 'playback',
      params: params
    })
  )

  ptz.value.classList.add('hidden')
}
// 停止视频流
const stopStream = () => {
  ws.send(
    JSON.stringify({
      method: 'stopStream'
    })
  )
}
// 获取海报帧
const fetchSnapshot = () => {
  ws.send(
    JSON.stringify({
      method: 'fetchSnapshot'
    })
  )
}

let intervalID = null
// 循环获取海报帧
const poseterInterval = () => {
  fetchSnapshot()
  intervalID = setInterval(() => {
    fetchSnapshot()
  }, 1000 * fetchSnapshotSecond)
}
// 消息处理
const onMessage = (ev) => {
  if (ev.data instanceof ArrayBuffer) return

  loading.value = false

  let data = JSON.parse(ev.data)

  if (data.type == 'initOnvif') {
    poseterInterval()
  }
  // 海报帧，检测设备在线
  if (data.type == 'fetchSnapshot') {
    const img = poster.value.querySelector('.poster-img')
    img.src = data.result

    onLine.value = data.code === 200
  }

  let type: any = 'info'
  if (data.code == 400) {
    type = 'error'

    ElMessage({
      message: data.message,
      type
    })
  }
}
// 点击播放
const play = () => {
  startStream()
  player.value.play()
  poster.value.style.display = 'none'
}
const stop = () => {
  stopStream()
  player.value.stop()
  poster.value.style.display = 'block'
}
// 初始化jsmpeg
const initJsmpeg = () => {
  player.value = new JSMpeg.Player(props.wsUrl, { canvas: canvas.value })
  //jsmpeg 首次加载时 ws的创建是异步
  const now = window.performance.now()
  const source = player.value.source
  const checkWs = (socket) => {
    if (socket) {
      ws = socket

      const wsReady = () => {
        initOnvif()
      }
      if (ws.readyState == 1) wsReady()
      else {
        ws.addEventListener('open', wsReady)
      }

      ws.addEventListener('message', onMessage)
    } else {
      if (window.performance.now() - now > 1000 * 60) return

      setTimeout(() => checkWs(source.socket), 100)
    }
  }

  checkWs(source.socket)
}

const destroyJsmpeg = () => {
  player.value.destroy()
}

onMounted(() => {
  initJsmpeg()
})

onUnmounted(() => {
  destroyJsmpeg()

  clearInterval(intervalID)
})

defineExpose({
  play,
  stop
})
</script>

<template>
  <div ref="container" class="container" v-loading="loading">
    <canvas ref="canvas"></canvas>
    <div class="on-line" :style="{ background: onLine ? '#0f0' : '#f00' }"></div>
    <div ref="poster" class="poster" @pointerdown="play">
      <img class="poster-img" v-show="onLine" />
      <div class="play" @pointerdown="startStream">
        <el-icon color="#fff" :size="50"><VideoPlay /></el-icon>
      </div>
    </div>
    <div ref="controls" class="controls hidden">
      <el-icon class="stop" @pointerdown="stop" color="#fff" :size="30">
        <VideoPause />
      </el-icon>
      <el-icon class="full-screen" @pointerup="fullScreen" color="#fff" :size="30">
        <FullScreen />
      </el-icon>
      <el-icon class="switch" @pointerdown="switchControls('hidden')" color="#fff" :size="30">
        <Menu />
      </el-icon>

      <div ref="ptz" class="ptz">
        <div
          class="ptz-button left-button"
          :style="computePath('left')"
          @pointerdown="ptzMove('left')"
          @pointerup="ptzStop"
        >
          <el-icon color="#fff" :size="30"><CaretLeft /></el-icon>
        </div>
        <div
          class="ptz-button top-button"
          :style="computePath('top')"
          @pointerdown="ptzMove('top')"
          @pointerup="ptzStop"
        >
          <el-icon color="#fff" :size="30"><CaretTop /></el-icon>
        </div>
        <div
          class="ptz-button right-button"
          :style="computePath('right')"
          @pointerdown="ptzMove('right')"
          @pointerup="ptzStop"
        >
          <el-icon color="#fff" :size="30"><CaretRight /></el-icon>
        </div>
        <div
          class="ptz-button bottom-button"
          :style="computePath('bottom')"
          @pointerdown="ptzMove('bottom')"
          @pointerup="ptzStop"
        >
          <el-icon color="#fff" :size="30"><CaretBottom /></el-icon>
        </div>
        <div class="ptz-button home-button" @pointerdown="ptzHome">
          <el-icon color="#fff" :size="30"><HomeFilled /></el-icon>
        </div>
        <div class="zoom">
          <div class="zoom-in" @pointerdown="ptzMove('in')" @pointerup="ptzStop">+</div>
          <div class="zoom-out" @pointerdown="ptzMove('out')" @pointerup="ptzStop">-</div>
        </div>
      </div>
      <div class="playback">
        <el-date-picker
          style="width: 200px"
          v-model="startTime"
          type="datetime"
          placeholder="选择开始时间"
        />
        <div class="playback-button" @click="playback">回放</div>
        <div class="playback-button" @click="startStream">预览</div>
      </div>
    </div>
  </div>
</template>

<style scoped>
.container {
  position: relative;
  overflow: hidden;
  width: 800px;
}

canvas {
  /* canvas的style不设height，宽高会保持比例 */
  width: 100%;
}

.on-line {
  width: 10px;
  height: 10px;
  border-radius: 50%;
  position: absolute;
  left: 10px;
  bottom: 10px;
  z-index: 10;
}

.poster {
  position: absolute;
  z-index: 2;
  top: 0;
  bottom: 0;
  left: 0;
  right: 0;
}

.poster-img {
  position: absolute;
  width: 100%;
  height: 100%;
}

.play {
  position: absolute;
  width: 100%;
  height: 100%;
  display: flex;
  justify-content: center;
  align-items: center;
  background: rgba(0, 0, 0, 0.5);
}

.controls {
  position: absolute;
  z-index: 1;
  top: 0;
  bottom: 0;
  left: 0;
  right: 0;
}

.stop {
  user-select: none;
  cursor: pointer;
  position: absolute;
  right: 90px;
  bottom: 10px;
  z-index: 999;
}

.full-screen {
  user-select: none;
  cursor: pointer;
  position: absolute;
  right: 50px;
  bottom: 10px;
  z-index: 999;
}

.switch {
  user-select: none;
  cursor: pointer;
  position: absolute;
  right: 10px;
  bottom: 10px;
  z-index: 999;
}

.ptz {
  --ptz-radius: 100px;
  user-select: none;
  width: calc(var(--ptz-radius) * 2);
  height: calc(var(--ptz-radius) * 2);
  position: absolute;
  left: 20px;
  bottom: 20px;
  transition: all 0.3s;
}

.ptz-button {
  cursor: pointer;
  width: 100%;
  height: 100%;
  position: absolute;
  left: 0;
  top: 0;
  display: flex;
  background: rgba(255, 255, 255, 0.3);
}

.ptz-button:active {
  background: rgba(255, 255, 255, 0.5);
}

.left-button {
  align-items: center;
}

.top-button {
  justify-content: center;
}

.right-button {
  align-items: center;
  justify-content: end;
}

.bottom-button {
  align-items: end;
  justify-content: center;
}

.home-button {
  clip-path: circle(50px at 100px 100px);
  display: flex;
  justify-content: center;
  align-items: center;
}

.zoom {
  position: absolute;
  top: -50px;
  left: 10px;
  display: flex;
  color: #fff;
  font-size: 20px;
  font-weight: 600;
}

.zoom > div {
  cursor: pointer;
  padding: 5px 20px;
  background: rgba(255, 255, 255, 0.3);
}

.zoom > div:active {
  background: rgba(255, 255, 255, 0.5);
}

.zoom-in {
  border-radius: 20px 0 0 20px;
}

.zoom-out {
  margin-left: 1px;
  border-radius: 0 20px 20px 0;
}

.playback {
  color: #fff;
  position: absolute;
  right: 50px;
  bottom: 50px;
  transition: all 0.5s;
  display: flex;
  align-items: center;
}

.playback > * {
  margin-left: 10px;
  margin-top: 5px;
}

.playback-button {
  user-select: none;
  cursor: pointer;
  color: #fff;
  background: rgba(255, 255, 255, 0.3);
  padding: 5px 10px;
  border-radius: 10px;
}

.playback-button:active {
  background: rgba(255, 255, 255, 0.5);
}

:deep(.el-input__wrapper) {
  --color: #fff;
  background: rgba(255, 255, 255, 0.3);
}

:deep(.el-input__inner) {
  color: #fff;
}

:deep(.el-input__inner)::placeholder {
  color: #fff;
}

.hidden .ptz,
.ptz.hidden,
.hidden .playback {
  bottom: -250px;
}

@media screen and (max-width: 900px) {
  .ptz {
    transform-origin: 0 100%;
    transform: scale(0.7);
    left: 5px;
    bottom: 5px;
  }

  .playback {
    transform-origin: 100% 0;
    transform: scale(0.8);
    flex-direction: column;
    align-items: end;
    right: 5px;
    bottom: 50px;
  }
}
</style>
